package com.atguigu.gmall.order.service.impl;

import com.alibaba.fastjson.JSON;
import com.atguigu.gmall.cart.client.CartFeignClient;
import com.atguigu.gmall.cart.model.CartInfo;
import com.atguigu.gmall.common.constant.RedisConst;
import com.atguigu.gmall.common.rabbit.config.MqConst;
import com.atguigu.gmall.common.rabbit.util.RabbitService;
import com.atguigu.gmall.common.util.DateUtil;
import com.atguigu.gmall.common.util.HttpClientUtil;
import com.atguigu.gmall.common.util.SnowFlake;
import com.atguigu.gmall.enums.model.OrderStatus;
import com.atguigu.gmall.enums.model.ProcessStatus;
import com.atguigu.gmall.order.model.OrderDetail;
import com.atguigu.gmall.order.model.OrderInfo;
import com.atguigu.gmall.order.mapper.OrderInfoMapper;
import com.atguigu.gmall.order.service.OrderDetailService;
import com.atguigu.gmall.order.service.OrderInfoService;
import com.atguigu.gmall.product.client.ProductFeignClient;
import com.atguigu.gmall.user.client.UserFeignClient;
import com.atguigu.gmall.user.model.UserAddress;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.aspectj.weaver.ast.Or;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Primary;
import org.springframework.data.redis.core.BoundHashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import java.math.BigDecimal;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * 订单表 订单表 业务实现类
 *
 * @author atguigu
 * @since 2023-06-25
 */
@Slf4j
@SuppressWarnings("all")
@Service
public class OrderInfoServiceImpl extends ServiceImpl<OrderInfoMapper, OrderInfo> implements OrderInfoService {

    @Autowired
    private UserFeignClient userFeignClient;


    @Autowired
    private ProductFeignClient productFeignClient;

    @Autowired
    private CartFeignClient cartFeignClient;


    @Autowired
    private ThreadPoolExecutor threadPoolExecutor;

    @Autowired
    private OrderDetailService orderDetailService;

    @Autowired
    private SnowFlake snowFlake;

    @Autowired
    private RedisTemplate redisTemplate;

    /**
     * 第三方库存系统基础路径
     */
    @Value("${ware.url}")
    private String wareUrl;


    /**
     * 渲染订单确认页面相关数据汇总
     * ${userAddressList}:地址列表
     * ${detailArrayList}:选中购物车商品
     * ${totalNum}:总数
     * ${totalAmount}:总金额
     *
     * @return
     */
    @Override
    public Map<String, Object> orderTradeData(String userId) {
        Map<String, Object> mapResult = new HashMap<>();
        Long userIdLong = Long.valueOf(userId);
        //1.远程调用用户服务获取收件地址列表
        CompletableFuture<Void> userAddressListCompletableFuture = CompletableFuture.runAsync(() -> {
            List<UserAddress> addressList = userFeignClient.getUserAddressListByUserId(userIdLong);
            mapResult.put("userAddressList", addressList);
        }, threadPoolExecutor);


        //2.远程调用购物车服务获取选中商品列表
        CompletableFuture<Void> orderDetailsCompletableFuture = CompletableFuture.runAsync(() -> {
            List<CartInfo> cartCheckedList = cartFeignClient.getCartCheckedList(userIdLong);
            //将获取到购物车集合转为订单明细集合
            if (!CollectionUtils.isEmpty(cartCheckedList)) {
                List<OrderDetail> orderDetailList = cartCheckedList.stream().map(cartInfo -> {
                    OrderDetail orderDetail = new OrderDetail();
                    orderDetail.setSkuId(cartInfo.getSkuId());
                    orderDetail.setImgUrl(cartInfo.getImgUrl());
                    orderDetail.setSkuName(cartInfo.getSkuName());
                    orderDetail.setSkuNum(cartInfo.getSkuNum());
                    orderDetail.setOrderPrice(cartInfo.getSkuPrice());
                    return orderDetail;
                }).collect(Collectors.toList());
                mapResult.put("detailArrayList", orderDetailList);
                //3.计算总金额以及总数量  调用订单实体类中计算金额方法 保证订单对象中封装订单明细集合
                OrderInfo orderInfo = new OrderInfo();
                orderInfo.setOrderDetailList(orderDetailList);
                orderInfo.sumTotalAmount();
                mapResult.put("totalAmount", orderInfo.getTotalAmount());
                mapResult.put("totalNum", orderDetailList.size());

                String tradeNo = this.generateTradeNo(userId);
                mapResult.put("tradeNo", tradeNo);
            }
        }, threadPoolExecutor);
        CompletableFuture.allOf(userAddressListCompletableFuture, orderDetailsCompletableFuture).join();
        return mapResult;
    }

    @Autowired
    private RabbitService rabbitService;

    /**
     * 提交普通商城订单
     *
     * @param orderInfo
     * @param tradeNo   用户提交流水号
     * @return 订单ID
     */
    @Override
    public Long submitOrder(OrderInfo orderInfo, String tradeNo) {
        //TODO 1.避免用户误操作使用浏览器回退重复提交订单-验证流水号
        Long userId = orderInfo.getUserId();
        //1.通过lua脚本验证流水号正确性,删除流水号 保证原子性
        String key = RedisConst.USER_KEY_PREFIX + userId + ":tradeno";
        String script = "if redis.call(\"get\",KEYS[1]) == ARGV[1]\n" +
                "then\n" +
                "    return redis.call(\"del\",KEYS[1])\n" +
                "else\n" +
                "    return 0\n" +
                "end";
        DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
        redisScript.setResultType(Long.class);
        redisScript.setScriptText(script);
        Long flag = (Long) redisTemplate.execute(redisScript, Arrays.asList(key), tradeNo);
        if (flag == 0) {
            throw new RuntimeException("请勿重复提交或者重试!");
        }

        //TODO 2.验证订单中包含商品是否有库存(调用库存系统),订单提交商品价格是否发生变化(商品服务)
        //2.1 获取所有订单中商品列表
        List<OrderDetail> orderDetailList = orderInfo.getOrderDetailList();
        ArrayList<String> errMsg = new ArrayList<>();
        ArrayList<CompletableFuture> allCompletableFutures = new ArrayList<>();
        if (!CollectionUtils.isEmpty(orderDetailList)) {

            orderDetailList.stream().forEach(orderDetail -> {
                //2.2 调用第三方"仓库管理系统"验证库存数量是否充足
                CompletableFuture<Void> stockCompletableFuture = CompletableFuture.runAsync(() -> {
                    //2.2.1 发起http请求调用库存系统restfulAPI验库存 方式1:HttpClient 方式2:URLConnection 方式3:Feign 方式4:RestTemplate 方式5:OkHttp
                    String wareResult = HttpClientUtil.doGet(wareUrl + "/hasStock?skuId=" + orderDetail.getSkuId() + "&num=" + orderDetail.getSkuNum());
                    //2.2.2 根据库存系统验证结果判断
                    if ("0".equals(wareResult)) {
                        //将校验异常信息收集
                        errMsg.add("商品:" + orderDetail.getSkuName() + ",商品库存不足!");
                    }
                }, threadPoolExecutor);
                allCompletableFutures.add(stockCompletableFuture);

                //2.3 调用商品服务获取商品最新价格 跟 订单中价格比较是否为最新
                CompletableFuture<Void> priceCompletableFuture = CompletableFuture.runAsync(() -> {
                    //2.3.1 获取商品最新价格
                    BigDecimal skuPrice = productFeignClient.getSkuPrice(orderDetail.getSkuId());
                    //2.3.2 比对价格发生变化
                    if (skuPrice.compareTo(orderDetail.getOrderPrice()) != 0) {
                        //2.3.3 如果价格修改提示用户价格失效,更新用户购物车中商品价格
                        String redisKey = RedisConst.USER_KEY_PREFIX + userId + RedisConst.USER_CART_KEY_SUFFIX;
                        BoundHashOperations<String, String, CartInfo> hashOps = redisTemplate.boundHashOps(redisKey);
                        String hashKey = orderDetail.getSkuId().toString();
                        if (hashOps.hasKey(hashKey)) {
                            CartInfo cartInfo = hashOps.get(hashKey);
                            cartInfo.setSkuPrice(skuPrice);
                            hashOps.put(hashKey, cartInfo);
                        }
                        //throw new RuntimeException("商品:" + orderDetail.getSkuName() + "价格失效,请重新下单!");
                        errMsg.add("商品:" + orderDetail.getSkuName() + ",价格失效!");
                    }
                }, threadPoolExecutor);
                allCompletableFutures.add(priceCompletableFuture);
            });

        }
        //组合多个校验任务
        CompletableFuture[] completableFutures = allCompletableFutures.toArray(new CompletableFuture[allCompletableFutures.size()]);
        CompletableFuture.allOf(completableFutures).join();
        //判断错误集合中是否有值
        if (!CollectionUtils.isEmpty(errMsg)) {
            String errorMessage = errMsg.stream().collect(Collectors.joining(","));
            throw new RuntimeException(errorMessage);
        }

        //3. 保存订单,以及订单明细
        String sourceType = "1";
        Long orderId = this.saveOrder(orderInfo, sourceType);

        //4.TODO 发送延迟关闭订单消息
        rabbitService.sendDelayMessage(MqConst.EXCHANGE_DIRECT_ORDER_CANCEL, MqConst.ROUTING_ORDER_CANCEL, orderId, 30);
        return orderId;
    }


    /**
     * 保存订单,以及订单明细
     *
     * @param orderInfo
     * @param sourceType 1:普通商城订单  2:秒杀订单
     * @return
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Long saveOrder(OrderInfo orderInfo, String sourceType) {
        //1.保存订单
        //1.1 订单中收件人信息前端提交
        //1.2 手动为其他属性赋值
        orderInfo.sumTotalAmount();
        orderInfo.setOrderStatus(OrderStatus.UNPAID.name());
        orderInfo.setProcessStatus(ProcessStatus.UNPAID.name());
        //生成全局唯一订单编号 "SPH"+时间戳   或者 雪花算法
        String outTradeNo = "";
        if ("1".equals(sourceType)) {
            outTradeNo = "SPH" + DateUtil.formatDate(new Date()) + snowFlake.nextId();
            Calendar calendar = Calendar.getInstance();
            calendar.add(Calendar.MINUTE, 30);  //订单失效时间 普通订单 30分钟
            orderInfo.setExpireTime(calendar.getTime());
        } else {
            outTradeNo = "SPHSC" + DateUtil.formatDate(new Date()) + snowFlake.nextId();
            Calendar calendar = Calendar.getInstance();
            calendar.add(Calendar.MINUTE, 5);  //订单失效时间 普通订单 5分钟
            orderInfo.setExpireTime(calendar.getTime());
        }
        orderInfo.setOutTradeNo(outTradeNo);
        List<OrderDetail> orderDetailList = orderInfo.getOrderDetailList();
        if (!CollectionUtils.isEmpty(orderDetailList)) {
            String tradeBody = orderDetailList.stream().map(OrderDetail::getSkuName).collect(Collectors.joining(","));
            if (tradeBody.length() > 100) {
                //TODO 截取subString截取后变量必须赋值
                tradeBody = tradeBody.substring(0, 100);
            }
            orderInfo.setTradeBody(tradeBody);
            orderInfo.setImgUrl(orderDetailList.get(0).getImgUrl());
        }
        orderInfo.setOperateTime(new Date());

        orderInfo.setProvinceId(1L);
        this.save(orderInfo);
        Long orderId = orderInfo.getId();


        //2.保存订单明细
        if (!CollectionUtils.isEmpty(orderDetailList)) {
            orderDetailList.stream().forEach(orderDetail -> {
                orderDetail.setOrderId(orderId);
                if ("1".equals(sourceType)) {
                    orderDetail.setSourceId(1L);
                    orderDetail.setSourceType("MALL");
                } else {
                    orderDetail.setSourceId(2L);
                    orderDetail.setSourceType("SECKILL");
                }
            });
            orderDetailService.saveBatch(orderDetailList);
        }
        return orderId;
    }


    /**
     * 生成流水号
     *
     * @param userId
     * @return
     */
    @Override
    public String generateTradeNo(String userId) {
        String key = RedisConst.USER_KEY_PREFIX + userId + ":tradeno";
        String value = UUID.randomUUID().toString().replaceAll("-", "");
        redisTemplate.opsForValue().set(key, value, 5, TimeUnit.MINUTES);
        return value;
    }

    /**
     * 验证流水号是否一致
     *
     * @param userId
     * @param tradeNo
     * @return
     */
    @Override
    public Boolean checkTradeNo(String userId, String tradeNo) {
        String key = RedisConst.USER_KEY_PREFIX + userId + ":tradeno";
        String redisTradeNo = (String) redisTemplate.opsForValue().get(key);
        return tradeNo.equals(redisTradeNo);
    }


    /**
     * 删除流水号
     *
     * @param userId
     */
    @Override
    public void deleteTradeNo(String userId) {
        String key = RedisConst.USER_KEY_PREFIX + userId + ":tradeno";
        redisTemplate.delete(key);
    }

    /**
     * 分页获取用户订单列表(包含订单明细)
     *
     * @param pageInfo 分页信息
     * @param userId   用户ID
     * @return
     */
    @Override
    public Page<OrderInfo> getOrderByPage(Page<OrderInfo> pageInfo, String userId) {
        //获取持久层对象调用动态SQL
        return this.getBaseMapper().getOrderByPage(pageInfo, userId);
    }


    /**
     * 关闭订单
     *
     * @param orderId
     */
    @Override
    public void execExpiredOrder(Long orderId) {
        //1.查询订单状态 判断状态 是否为待支付
        OrderInfo orderInfo = this.getById(orderId);
        if (orderInfo != null && (OrderStatus.UNPAID.name().equals(orderInfo.getOrderStatus()) || OrderStatus.PAID.name().equals(orderInfo.getOrderStatus()))) {
            //2.修改为关闭
            this.updateOrderStatus(orderId, ProcessStatus.CLOSED);
            //3.发送关闭订单消息到MQ通知支付系统关闭交易记录
            rabbitService.sendMessage(MqConst.EXCHANGE_DIRECT_PAYMENT_CLOSE, MqConst.ROUTING_PAYMENT_CLOSE, orderId);
        }
    }

    /**
     * 将订单改为指定状态
     *
     * @param orderId
     * @param processStatus
     */
    @Override
    public void updateOrderStatus(Long orderId, ProcessStatus processStatus) {
        OrderInfo orderInfo = new OrderInfo();
        orderInfo.setId(orderId);
        orderInfo.setOrderStatus(processStatus.getOrderStatus().name());
        orderInfo.setProcessStatus(processStatus.name());
        this.updateById(orderInfo);
    }

    /**
     * 查询订单信息(包含订单明细)
     *
     * @param orderId
     * @return
     */
    @Override
    public OrderInfo getOrderInfo(Long orderId) {
        OrderInfo orderInfo = this.getById(orderId);
        if (orderInfo != null) {
            LambdaQueryWrapper<OrderDetail> queryWrapper = new LambdaQueryWrapper<>();
            queryWrapper.eq(OrderDetail::getOrderId, orderId);
            List<OrderDetail> orderDetailList = orderDetailService.list(queryWrapper);
            orderInfo.setOrderDetailList(orderDetailList);
        }
        return orderInfo;
    }

    /**
     * 向第三方"库存系统"发送锁定商品库存消息到MQ
     *
     * @param orderId
     */
    @Override
    public void sendLockStockMsg(Long orderId) {
        //1.根据订单ID查询订单信息以及明细
        OrderInfo orderInfo = this.getOrderInfo(orderId);

        //2.构建消息所需Map按照接口文档设置属性
        Map<String, Object> wareMap = this.initWareMap(orderInfo);

        //3.发送消息到MQ
        rabbitService.sendMessage(MqConst.EXCHANGE_DIRECT_WARE_STOCK, MqConst.ROUTING_WARE_STOCK, JSON.toJSONString(wareMap));
    }


    /**
     * 库存系统所需要参数
     *
     * @param orderInfo
     * @return
     */
    private Map<String, Object> initWareMap(OrderInfo orderInfo) {
        //1.构建结果MAP集合对象
        HashMap<String, Object> mapResult = new HashMap<>();
        //2.封装订单信息
        if (orderInfo != null) {
            mapResult.put("orderId", orderInfo.getId());
            mapResult.put("consignee", orderInfo.getConsignee());
            mapResult.put("consigneeTel", orderInfo.getConsigneeTel());
            mapResult.put("orderComment", orderInfo.getOrderComment());
            mapResult.put("orderBody", orderInfo.getTradeBody());
            mapResult.put("deliveryAddress", orderInfo.getDeliveryAddress());
            mapResult.put("paymentWay", "1");
            mapResult.put("wareId", orderInfo.getWareId());
        }
        //3.封装订单明细信息
        List<OrderDetail> orderDetailList = orderInfo.getOrderDetailList();
        if (!CollectionUtils.isEmpty(orderDetailList)) {
            List<HashMap<String, Object>> details = orderDetailList.stream().map(orderDetail -> {
                HashMap<String, Object> detail = new HashMap<>();
                detail.put("skuId", orderDetail.getSkuId());
                detail.put("skuNum", orderDetail.getSkuNum());
                detail.put("skuName", orderDetail.getSkuName());
                return detail;
            }).collect(Collectors.toList());
            mapResult.put("details", details);
        }
        return mapResult;
    }


    /**
     * 拆单业务方法
     * 本质:将原始订单,按照所属仓库拆分为多个新子订单,以及新订单明细
     *
     * @param orderId    订单ID
     * @param wareSkuMap 仓库跟商品对照关系 wareSkuMap:"[{"wareId":"1","skuIds":["24"]},{"wareId":"2","skuIds":["36"]}]"
     * @return
     */
    @Override
    public String orderSplit(Long orderId, String wareSkuMap) {
        if (orderId != null && StringUtils.isNotBlank(wareSkuMap)) {
            log.info("orderId:{}, wareSkuMap:{}", orderId, wareSkuMap);
            //1.根据订单ID查询原始订单,以及订单明细
            OrderInfo originOrderInfo = this.getOrderInfo(orderId);
            List<OrderDetail> orginOrderDetailList = originOrderInfo.getOrderDetailList();

            //2.遍历仓库系统提交仓库跟SKU对照MAP 没遍历一次产生新子订单包含订单明细
            //2.1 将对照关系JSON数组字符串转为集合 泛型:Map--->List<Map>
            List<Map> wareSkuMapList = JSON.parseArray(wareSkuMap, Map.class);
            List<OrderInfo> newSubOrderInfoList = wareSkuMapList.stream().map(skuWareMap -> {
                //2.2 创建新子订单对象,子订单对象中属性值来源于原始订单
                OrderInfo newSubOrderInfo = new OrderInfo();
                BeanUtils.copyProperties(originOrderInfo, newSubOrderInfo);
                //2.2.1 避免主键冲突 设置为订单为null
                newSubOrderInfo.setId(null);
                //2.2.2 设置父订单编号
                newSubOrderInfo.setParentOrderId(originOrderInfo.getId());


                //2.4 获取当前新订单中订单明细
                String wareId = (String) skuWareMap.get("wareId");
                //当前订单对象设置仓库ID
                newSubOrderInfo.setWareId(wareId);
                List<String> skuIds = (List<String>) skuWareMap.get("skuIds");
                //2.4.1 对原始订单明细集合进行过滤 过滤条件:skuIds包含订单明细ID
                List<OrderDetail> newSubOrderDetailList = orginOrderDetailList.stream().filter(orginOrderDetail -> {
                    //返回true 符合要求数据
                    return skuIds.contains(orginOrderDetail.getSkuId().toString());
                }).collect(Collectors.toList());

                //2.3 保存子订单
                newSubOrderInfo.setOrderDetailList(newSubOrderDetailList);
                newSubOrderInfo.sumTotalAmount();
                this.save(newSubOrderInfo);


                //2.4.2 保存子订单明细 重新设置所属订单
                newSubOrderDetailList.stream().forEach(newSubOrderDetailInfo -> {
                    newSubOrderDetailInfo.setOrderId(newSubOrderInfo.getId());
                });
                orderDetailService.saveBatch(newSubOrderDetailList);

                return newSubOrderInfo;
            }).collect(Collectors.toList());

            //3.原始订单状态修改为拆单"SPLIT"
            originOrderInfo.setOrderStatus(OrderStatus.SPLIT.name());
            this.updateById(originOrderInfo);

            //4.按照仓库系统要求响应结果放回拆分后子订单集合
            List<Map<String, Object>> collect = newSubOrderInfoList.stream().map(newOrder -> {
                return initWareMap(newOrder);
            }).collect(Collectors.toList());
            return JSON.toJSONString(collect);
        }

        return null;
    }

    /**
     * 提交保存秒杀订单
     *
     * @param orderInfo
     * @return
     */
    @Override
    public Long submitSeckillOrder(OrderInfo orderInfo) {
        //1.保存秒杀订单
        String sourceType = "2";
        Long orderId = this.saveOrder(orderInfo, sourceType);
        //2.发送延迟关闭订单消息
        rabbitService.sendDelayMessage(MqConst.EXCHANGE_DIRECT_ORDER_CANCEL, MqConst.ROUTING_ORDER_CANCEL, orderId, 300);
        return orderId;

    }

}
